/*
 * Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr.
 *
 * This file is part of the SeaGlass Pluggable Look and Feel.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * $Id: ShapeUtil.java 968 2010-01-29 12:42:08Z kathryn@kathrynhuxtable.org $
 */
package com.seaglasslookandfeel.painter.util;

import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;

/**
 * Return various shapes used by the Painter classes.
 * 
 * @author Kathryn Huxtable
 */
/**
 * @author Kathryn Huxtable
 * 
 */
public class ShapeUtil {

    /**
     * The style of a particular corner.
     */
    public enum CornerStyle {
        /**
         * Make a square corner.
         */
        SQUARE,

        /**
         * Make a rounded corner.
         */
        ROUNDED,
    };

    /**
     * The rounding amount for a corner.
     */
    /**
     * @author Kathryn Huxtable
     * 
     */
    public enum CornerSize {
        /**
         * Round using half the height, producing a nice quarter of a circle for
         * the entire quarter of the rectangle. Use for horizontal lozenges that
         * are filled.
         */
        ROUND_HEIGHT(0),
        /**
         * Round using half the height plus 1, producing a nice quarter of a
         * circle for the entire quarter of the rectangle. Use for horizontal
         * lozenges that are drawn.
         */
        ROUND_HEIGHT_DRAW(0),
        /**
         * Round using half the width, producing a nice quarter of a circle for
         * the entire quarter of the rectangle. Use for vertical lozenges that
         * are filled.
         */
        ROUND_WIDTH(0),
        /**
         * Round using half the width plus 1, producing a nice quarter of a
         * circle for the entire quarter of the rectangle. Use for vertical
         * lozenges that are drawn.
         */
        ROUND_WIDTH_DRAW(0),

        /**
         * Round for a generic object's interior.
         */
        INTERIOR(baseRadius - 1),
        /**
         * Round for a generic object's border.
         */
        BORDER(baseRadius),
        /**
         * Round for a generic object's inner focus ring.
         */
        INNER_FOCUS(baseRadius + 1),
        /**
         * Round for a generic object's outer focus ring.
         */
        OUTER_FOCUS(baseRadius + 2),

        /**
         * Round for a slider thumb's interior.
         */
        SLIDER_INTERIOR(baseRadius - 2),
        /**
         * Round for a slider thumb's border.
         */
        SLIDER_BORDER(baseRadius - 1),
        /**
         * Round for a slider thumb's inner focus ring.
         */
        SLIDER_INNER_FOCUS(baseRadius),
        /**
         * Round for a slider thumb's outer focus ring.
         */
        SLIDER_OUTER_FOCUS(baseRadius + 1),

        /**
         * Round for a check box's interior.
         */
        CHECKBOX_INTERIOR(baseRadius / 2),
        /**
         * Round for a check box's border.
         */
        CHECKBOX_BORDER((baseRadius + 1) / 2),
        /**
         * Round for a check box's inner focus ring.
         */
        CHECKBOX_INNER_FOCUS((baseRadius + 2) / 2),
        /**
         * Round for a check box's outer focus ring.
         */
        CHECKBOX_OUTER_FOCUS((baseRadius + 3) / 2),

        /**
         * Round for a popup menu's border.
         */
        POPUP_BORDER(3),
        /**
         * Round for a popup menu's interior.
         */
        POPUP_INTERIOR(2.5);

        /**
         * The rounding radius.
         */
        private double radius;

        /**
         * Create the corner size.
         * 
         * @param radius
         *            the radius for rounding.
         */
        CornerSize(double radius) {
            this.radius = radius;
        }

        /**
         * Return the rounding radius. Note that the {@code ROUND*} values are
         * handled specially, as the rounding is dependent on the size of the
         * rectangle.
         * 
         * @param w
         *            the width of the rectangle.
         * @param h
         *            the height of the rectangle.
         * 
         * @return the radius (arc size) to use for rounding.
         */
        public double getRadius(int w, int h) {
            switch (this) {
            case ROUND_HEIGHT:
                return h / 2.0;
            case ROUND_HEIGHT_DRAW:
                return (h + 1) / 2.0;
            case ROUND_WIDTH:
                return w / 2.0;
            case ROUND_WIDTH_DRAW:
                return (w + 1) / 2.0;
            default:
                return radius;
            }
        }
    }

    /**
     * The base radius (arc size) for most control's borders. This is used to
     * calculate the rest of the arc sizes in a relative manner.
     */
    private static final double baseRadius = 4d;

    /**
     * Used for generic shapes.
     */
    private static Path2D       path       = new Path2D.Double(Path2D.WIND_EVEN_ODD);

    /**
     * Used for simple elliptical or circular shapes.
     */
    private static Ellipse2D    ellipse    = new Ellipse2D.Float();

    /**
     * Return a path for a rectangle with square corners.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the rectangle
     * @param y
     *            the Y coordinate of the upper-left corner of the rectangle
     * @param w
     *            the width of the rectangle
     * @param h
     *            the height of the rectangle
     * 
     * @return a path representing the shape.
     */
    public static Shape createRectangle(final int x, final int y, final int w, final int h) {
        return createRoundRectangleInternal(x, y, w, h, 0, CornerStyle.SQUARE, CornerStyle.SQUARE, CornerStyle.SQUARE, CornerStyle.SQUARE);
    }

    /**
     * Return a path for a rectangle with rounded corners.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the rectangle
     * @param y
     *            the Y coordinate of the upper-left corner of the rectangle
     * @param w
     *            the width of the rectangle
     * @param h
     *            the height of the rectangle
     * @param size
     *            the CornerSize value representing the amount of rounding
     * 
     * @return a path representing the shape.
     */
    public static Shape createRoundRectangle(final int x, final int y, final int w, final int h, final CornerSize size) {
        return createRoundRectangle(x, y, w, h, size, CornerStyle.ROUNDED, CornerStyle.ROUNDED, CornerStyle.ROUNDED, CornerStyle.ROUNDED);
    }

    /**
     * Return a path for a rectangle with optionally rounded corners.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the rectangle
     * @param y
     *            the Y coordinate of the upper-left corner of the rectangle
     * @param w
     *            the width of the rectangle
     * @param h
     *            the height of the rectangle
     * @param size
     *            the CornerSize value representing the amount of rounding
     * @param topLeft
     *            the CornerStyle of the upper-left corner. This must be one of
     *            {@code CornerStyle.SQUARE} or {@code CornerStyle.ROUNDED}.
     * @param bottomLeft
     *            the CornerStyle of the lower-left corner. This must be one of
     *            {@code CornerStyle.SQUARE} or {@code CornerStyle.ROUNDED}.
     * @param bottomRight
     *            the CornerStyle of the lower-right corner. This must be one of
     *            {@code CornerStyle.SQUARE} or {@code CornerStyle.ROUNDED}.
     * @param topRight
     *            the CornerStyle of the upper-right corner. This must be one of
     *            {@code CornerStyle.SQUARE} or {@code CornerStyle.ROUNDED}.
     * 
     * @return a path representing the shape.
     */
    public static Shape createRoundRectangle(final int x, final int y, final int w, final int h, final CornerSize size,
        final CornerStyle topLeft, final CornerStyle bottomLeft, final CornerStyle bottomRight, final CornerStyle topRight) {
        return createRoundRectangleInternal(x, y, w, h, size.getRadius(w, h), topLeft, bottomLeft, bottomRight, topRight);
    }

    /**
     * Return a path for a rectangle with square corners and no right side. This
     * is used for text fields that are part of a larger control, which is
     * placed to their left, e.g. spinners and editable combo boxes.
     * <p>
     * This path is suitable for drawing, not for filling.
     * </p>
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the rectangle
     * @param y
     *            the Y coordinate of the upper-left corner of the rectangle
     * @param w
     *            the width of the rectangle
     * @param h
     *            the height of the rectangle
     * 
     * @return a path representing the shape.
     */
    public static Shape createOpenRectangle(final int x, final int y, final int w, final int h) {
        path.reset();
        path.moveTo(x + w, y);
        path.lineTo(x, y);
        path.lineTo(x, y + h);
        path.lineTo(x + w, y + h);
        return path;
    }

    /**
     * Return a path for a check mark.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the check mark
     * @param y
     *            the Y coordinate of the upper-left corner of the check mark
     * @param w
     *            the width of the check mark
     * @param h
     *            the height of the check mark
     * 
     * @return a path representing the shape.
     */
    public static Shape createCheckMark(final int x, final int y, final int w, final int h) {
        double xf = w / 12.0;
        double hf = h / 12.0;

        path.reset();
        path.moveTo(x, y + 7.0 * hf);
        path.lineTo(x + 2.0 * xf, y + 7.0 * hf);
        path.lineTo(x + 4.75 * xf, y + 10.0 * hf);
        path.lineTo(x + 9.0 * xf, y);
        path.lineTo(x + 11.0 * xf, y);
        path.lineTo(x + 5.0 * xf, y + 12.0 * hf);
        path.closePath();
        return path;
    }

    /**
     * Return a path for an arrow pointing to the left.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the arrow
     * @param y
     *            the Y coordinate of the upper-left corner of the arrow
     * @param w
     *            the width of the arrow
     * @param h
     *            the height of the arrow
     * 
     * @return a path representing the shape.
     */
    public static Shape createArrowLeft(final double x, final double y, final double w, final double h) {
        path.reset();
        path.moveTo(x + w, y);
        path.lineTo(x, y + h / 2.0);
        path.lineTo(x + w, y + h);
        path.closePath();
        return path;
    }

    /**
     * Return a path for an arrow pointing to the right.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the arrow
     * @param y
     *            the Y coordinate of the upper-left corner of the arrow
     * @param w
     *            the width of the arrow
     * @param h
     *            the height of the arrow
     * 
     * @return a path representing the shape.
     */
    public static Shape createArrowRight(final double x, final double y, final double w, final double h) {
        path.reset();
        path.moveTo(x, y);
        path.lineTo(x + w, y + h / 2);
        path.lineTo(x, y + h);
        path.closePath();
        return path;
    }

    /**
     * Return a path for an arrow pointing up.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the arrow
     * @param y
     *            the Y coordinate of the upper-left corner of the arrow
     * @param w
     *            the width of the arrow
     * @param h
     *            the height of the arrow
     * 
     * @return a path representing the shape.
     */
    public static Shape createArrowUp(final double x, final double y, final double w, final double h) {
        path.reset();
        path.moveTo(x, y + h);
        path.lineTo(x + w / 2, y);
        path.lineTo(x + w, y + h);
        path.closePath();
        return path;
    }

    /**
     * Return a path for an arrow pointing down.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the arrow
     * @param y
     *            the Y coordinate of the upper-left corner of the arrow
     * @param w
     *            the width of the arrow
     * @param h
     *            the height of the arrow
     * 
     * @return a path representing the shape.
     */
    public static Shape createArrowDown(final double x, final double y, final double w, final double h) {
        path.reset();
        path.moveTo(x, y);
        path.lineTo(x + w / 2, y + h);
        path.lineTo(x + w, y);
        path.closePath();
        return path;
    }

    /**
     * Return a path for the patterned portion of an indeterminate progress bar.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the region
     * @param y
     *            the Y coordinate of the upper-left corner of the region
     * @param w
     *            the width of the region
     * @param h
     *            the height of the region
     * 
     * @return a path representing the shape.
     */
    public static Shape createProgressBarIndeterminatePattern(int x, int y, int width, int height) {
        final double wHalf = width / 2.0;
        final double xOffset = wHalf / 4.0;

        path.reset();
        path.moveTo(xOffset, 0);
        path.lineTo(wHalf + xOffset, 0);
        path.lineTo(width, height);
        path.lineTo(wHalf, height);
        path.closePath();

        return path;
    }

    /**
     * Return a path for a rounded internal drop shadow. This is used for
     * progress bar tracks and search fields.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the shadow
     * @param y
     *            the Y coordinate of the upper-left corner of the shadow
     * @param w
     *            the width of the shadow
     * @param h
     *            the height of the rectangle
     * 
     * @return a path representing the shadow.
     */
    public static Shape createInternalDropShadowRounded(final int x, final int y, final int w, final int h) {
        final double radius = h / 2;
        final int right = x + w;
        final double bottom = y + radius;

        path.reset();

        // Upper edge.
        path.moveTo(x, bottom);
        path.quadTo(x, y, x + radius, y);
        path.lineTo(right - radius, y);
        path.quadTo(right, y, right, bottom);

        // Lower edge.
        path.lineTo(right - 1, bottom);
        path.quadTo(right - 2, y + 2, right - radius - 1, y + 2);
        path.lineTo(x + radius + 1, y + 2);
        path.quadTo(x + 2, y + 2, x + 1, bottom);

        path.closePath();
        return path;
    }

    /**
     * Return a path for a focus rectangle.
     * <p>
     * This path is suitable for filling.
     * </p>
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the rectangle
     * @param y
     *            the Y coordinate of the upper-left corner of the rectangle
     * @param w
     *            the width of the rectangle
     * @param h
     *            the height of the rectangle
     * 
     * @return a path representing the shape.
     */
    public static Shape createFillableFocusRectangle(int x, int y, int width, int height) {
        final int left = x;
        final int top = y;
        final int right = x + width;
        final int bottom = y + height;

        path.reset();
        path.moveTo(left, top);
        path.lineTo(left, bottom);
        path.lineTo(right, bottom);
        path.lineTo(right, top);

        final float left2 = left - 1.4f;
        final float top2 = top - 1.4f;
        final float right2 = right + 1.4f;
        final float bottom2 = bottom + 1.4f;

        // TODO These two lines were curveTo in Nimbus. Perhaps we should
        // revisit this?
        path.lineTo(right2, top);
        path.lineTo(right2, bottom2);
        path.lineTo(left2, bottom2);
        path.lineTo(left2, top2);
        path.lineTo(right2, top2);
        path.lineTo(right2, top);
        path.closePath();

        return path;
    }

    /**
     * Return a path for a simple bullet.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the bullet
     * @param y
     *            the Y coordinate of the upper-left corner of the bullet
     * @param diameter
     *            the diameter of the bullet
     * 
     * @return a path representing the shape.
     */
    public static Shape createBullet(int x, int y, int diameter) {
        return createEllipseInternal(x, y, diameter, diameter);
    }

    /**
     * Return a path for a radio button's concentric sections.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the section
     * @param y
     *            the Y coordinate of the upper-left corner of the section
     * @param diameter
     *            the diameter of the section
     * 
     * @return a path representing the shape.
     */
    public static Shape createRadioButton(int x, int y, int diameter) {
        return createEllipseInternal(x, y, diameter, diameter);
    }

    /**
     * Return a path for a continuous slider thumb's concentric sections.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the section
     * @param y
     *            the Y coordinate of the upper-left corner of the section
     * @param diameter
     *            the diameter of the section
     * 
     * @return a path representing the shape.
     */
    public static Shape createSliderThumbContinuous(final int x, final int y, final int diameter) {
        return createEllipseInternal(x, y, diameter, diameter);
    }

    /**
     * Return a path for a discrete slider thumb's concentric sections.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the section
     * @param y
     *            the Y coordinate of the upper-left corner of the section
     * @param w
     *            the width of the section
     * @param h
     *            the height of the section
     * @param size
     *            the CornerSize representing the rounding amount for the
     *            section
     * 
     * @return a path representing the shape.
     */
    public static Shape createSliderThumbDiscrete(final int x, final int y, final int w, final int h, final CornerSize size) {
        final double topArc = size.getRadius(w, h);
        final double bottomArcH = size == CornerSize.INTERIOR ? 0 : 1;
        final double bottomArcW = 3;

        path.reset();
        path.moveTo(x, y + topArc);
        path.quadTo(x, y, x + topArc, y);
        path.lineTo(x + w - topArc, y);
        path.quadTo(x + w, y, x + w, y + topArc);
        path.lineTo(x + w, y + h / 2.0);
        path.quadTo(x + w - bottomArcW, y + h - bottomArcH, x + w / 2.0, y + h);
        path.quadTo(x + bottomArcW, y + h - bottomArcH, x, y + h / 2.0);
        path.closePath();
        return path;
    }

    /**
     * Return a path for a "cancel" icon. This is a circle with a punched out
     * "x" in it.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the icon
     * @param y
     *            the Y coordinate of the upper-left corner of the icon
     * @param w
     *            the width of the icon
     * @param h
     *            the height of the icon
     * 
     * @return a path representing the shape.
     */
    public static Shape createCancelIcon(int x, int y, int width, int height) {
        final double xMid = x + width / 2.0;
        final double yMid = y + height / 2.0;

        // Draw the circle.
        path.reset();
        path.moveTo(xMid, y);
        path.quadTo(x, y, x, yMid);
        path.quadTo(x, y + height, xMid, y + height);
        path.quadTo(x + width, y + height, x + width, yMid);
        path.quadTo(x + width, y, xMid, y);
        path.closePath();

        final double xOffsetL = width / 2.0 - 3;
        final double xOffsetS = width / 2.0 - 4;
        final double yOffsetL = height / 2.0 - 3;
        final double yOffsetS = height / 2.0 - 4;
        final double offsetC = 1.5;

        // Erase the "x" with an inner subpath.
        path.moveTo(xMid, yMid - offsetC);
        path.lineTo(xMid + xOffsetS, yMid - yOffsetL);
        path.lineTo(yMid + xOffsetL, yMid - yOffsetS);
        path.lineTo(xMid + offsetC, yMid);
        path.lineTo(xMid + xOffsetL, yMid + yOffsetS);
        path.lineTo(xMid + xOffsetS, yMid + yOffsetL);
        path.lineTo(xMid, yMid + offsetC);
        path.lineTo(xMid - xOffsetS, yMid + yOffsetL);
        path.lineTo(xMid - xOffsetL, yMid + yOffsetS);
        path.lineTo(xMid - offsetC, yMid);
        path.lineTo(xMid - xOffsetL, yMid - yOffsetS);
        path.lineTo(xMid - xOffsetS, yMid - yOffsetL);
        path.closePath();

        return path;
    }

    /**
     * Return a path for a scroll bar cap. This is used when the buttons are
     * placed together at the opposite end of the scroll bar.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the cap
     * @param y
     *            the Y coordinate of the upper-left corner of the cap
     * @param w
     *            the width of the cap
     * @param h
     *            the height of the cap
     * 
     * @return a path representing the shape.
     */
    public static Shape createScrollCap(int x, int y, int width, int height) {
        path.reset();
        path.moveTo(x, y);
        path.lineTo(x, y + height);
        path.lineTo(x + width, y + height);
        addScrollGapPath(x, y, width, height, true);
        path.closePath();
        return path;
    }

    /**
     * Return a path for a scroll bar button. This is used when the buttons are
     * placed apart at opposite ends of the scroll bar. This is a common shape
     * that is transformed to the appropriate button.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the button
     * @param y
     *            the Y coordinate of the upper-left corner of the button
     * @param w
     *            the width of the button
     * @param h
     *            the height of the button
     * 
     * @return a path representing the shape.
     */
    public static Shape createScrollButtonApart(int x, int y, int width, int height) {
        path.reset();
        path.moveTo(x, y);
        path.lineTo(x, y + height);
        path.lineTo(x + width, y + height);
        addScrollGapPath(x, y, width, height, true);
        path.closePath();
        return path;
    }

    /**
     * Return a path for a scroll bar decrease button. This is used when the
     * buttons are placed together at one end of the scroll bar.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the button
     * @param y
     *            the Y coordinate of the upper-left corner of the button
     * @param w
     *            the width of the button
     * @param h
     *            the height of the button
     * 
     * @return a path representing the shape.
     */
    public static Shape createScrollButtonTogetherDecrease(int x, int y, int width, int height) {
        path.reset();
        path.moveTo(x + width, y);
        path.lineTo(x + width, y + height);
        path.lineTo(x, y + height);
        addScrollGapPath(x, y, width, height, false);
        path.closePath();
        return path;
    }

    /**
     * Return a path for a scroll bar increase button. This is used when the
     * buttons are placed together at one end of the scroll bar.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the button
     * @param y
     *            the Y coordinate of the upper-left corner of the button
     * @param w
     *            the width of the button
     * @param h
     *            the height of the button
     * 
     * @return a path representing the shape.
     */
    public static Shape createScrollButtonTogetherIncrease(int x, int y, int w, int h) {
        return createRectangle(x, y, w, h);
    }

    /**
     * Adds a hemispherical section to the current path. This is used to create
     * the gap in a scroll bar button or cap into which the scroll bar thumb
     * will fit.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the button or cap
     * @param y
     *            the Y coordinate of the upper-left corner of the button or cap
     * @param w
     *            the width of the button or cap
     * @param h
     *            the height of the button or cap
     * @param isAtLeft
     *            {@code true} if the gap is at the left end of the button,
     *            {@code false} if it is at the right.
     */
    private static void addScrollGapPath(int x, int y, int width, int height, boolean isAtLeft) {
        final double hHalf = height / 2.0;
        final double wFull = isAtLeft ? width : 0;
        final double wHalfOff = isAtLeft ? width - hHalf : hHalf;

        path.quadTo(x + wHalfOff, y + height, x + wHalfOff, y + hHalf);
        path.quadTo(x + wHalfOff, y, x + wFull, y);
    }

    /**
     * Return a path for an ellipse.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the ellipse
     * @param y
     *            the Y coordinate of the upper-left corner of the ellipse
     * @param w
     *            the width of the ellipse
     * @param h
     *            the height of the ellipse
     * 
     * @return a path representing the shape.
     */
    private static Shape createEllipseInternal(int x, int y, int w, int h) {
        ellipse.setFrame(x, y, w, h);
        return ellipse;
    }

    /**
     * Return a path for a rectangle with optionally rounded corners.
     * 
     * @param x
     *            the X coordinate of the upper-left corner of the rectangle
     * @param y
     *            the Y coordinate of the upper-left corner of the rectangle
     * @param w
     *            the width of the rectangle
     * @param h
     *            the height of the rectangle
     * @param radius
     *            the radius (arc size) used for rounding
     * @param topLeft
     *            the CornerStyle of the upper-left corner. This must be one of
     *            {@code CornerStyle.SQUARE} or {@code CornerStyle.ROUNDED}.
     * @param bottomLeft
     *            the CornerStyle of the lower-left corner. This must be one of
     *            {@code CornerStyle.SQUARE} or {@code CornerStyle.ROUNDED}.
     * @param bottomRight
     *            the CornerStyle of the lower-right corner. This must be one of
     *            {@code CornerStyle.SQUARE} or {@code CornerStyle.ROUNDED}.
     * @param topRight
     *            the CornerStyle of the upper-right corner. This must be one of
     *            {@code CornerStyle.SQUARE} or {@code CornerStyle.ROUNDED}.
     * 
     * @return a path representing the shape.
     */
    private static Shape createRoundRectangleInternal(final int x, final int y, final int w, final int h, final double radius,
        final CornerStyle topLeft, final CornerStyle bottomLeft, final CornerStyle bottomRight, final CornerStyle topRight) {
        // Convenience variables.
        final int left = x;
        final int top = y;
        final int right = x + w;
        final int bottom = y + h;

        // Start the path.
        path.reset();
        // Move to top left and draw rounded corner if requested.
        switch (topLeft) {
        case SQUARE:
            path.moveTo(left, top);
            break;
        case ROUNDED:
            path.moveTo(left + radius, top);
            path.quadTo(left, top, left, top + radius);
            break;
        }
        // Draw through bottom left corner.
        switch (bottomLeft) {
        case SQUARE:
            path.lineTo(left, bottom);
            break;
        case ROUNDED:
            path.lineTo(left, bottom - radius);
            path.quadTo(left, bottom, left + radius, bottom);
            break;
        }
        // Draw through bottom right corner.
        switch (bottomRight) {
        case SQUARE:
            path.lineTo(right, bottom);
            break;
        case ROUNDED:
            path.lineTo(right - radius, bottom);
            path.quadTo(right, bottom, right, bottom - radius);
        }
        // Draw through top right corner.
        switch (topRight) {
        case SQUARE:
            path.lineTo(right, top);
            break;
        case ROUNDED:
            path.lineTo(right, top + radius);
            path.quadTo(right, top, right - radius, top);
            break;
        }
        // Close the path.
        path.closePath();
        return path;
    }
}
